#pragma once
#include "log.hpp"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include "log.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <sys/select.h>
#include <array>

namespace selectServer
{

    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    class SelectServer
    {
        // static const uint16_t dport = 20000;
        static const uint16_t defport;
        static const int gbacklog = 20;

    public:
        SelectServer(const uint16_t port = defport) : _sock(-1), _port(port), _fdarray(nullptr)
        {
        }

        ~SelectServer()
        {
            if (_sock > 0)
                close(_sock);
            if (_fdarray)
                delete[] _fdarray;
        }

        void initServer()
        {
            // 创建套接字
            _sock = socket(AF_INET, SOCK_STREAM, 0);
            if (_sock < 0)
            {
                logcout(FATAL, "create socket error !!");
                exit(SOCKET_ERR);
            }
            logcout(NORMAL, "create socket error !!");

            // 设置地址复用  保证立即重启
            int opt = 1;
            setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 绑定bind
            struct sockaddr_in addr;
            addr.sin_port = htons(_port);
            addr.sin_family = AF_INET;
            // addr.sin_family= PF_INET;  一样的

            addr.sin_addr.s_addr = inet_addr("0.0.0.0");
            addr.sin_addr.s_addr = INADDR_ANY;

            int ret = bind(_sock, (struct sockaddr *)&addr, sizeof(addr));
            if (ret < 0)
            {
                logcout(FATAL, "bind socket error!!");
                exit(BIND_ERR);
            }
            logcout(NORMAL, "bind socket success!!");

            // 设置监听状态

            ret = listen(_sock, gbacklog);
            if (ret < 0)
            {
                logcout(FATAL, "listen socket error!!");
                exit(LISTEN_ERR);
            }
            logcout(NORMAL, "listen socket success!!");

            _fdarray = new int[1024];
            // 这里的 fdarray 的 大小是什么  这个跟fd_set 相关, fd_set是个类型 一定有上限
            // sizeof(fd_set) == 128
            // 这里的 128是字节的位图结构
            //  这里一共可以表示 128*8 == 1024 个文件描述符
            // 所以fdarray的大小就是 1024
            for (int i = 0; i < 1024; i++)
            {
                _fdarray[i] = -1;
            } // 初始化

            _fdarray[0] = _sock; // 添加 lisentsock
        }

        void start()
        {
            for (;;)
            {
                fd_set rfds;
                FD_ZERO(&rfds);

                int max = _fdarray[0];
                for (int i = 0; i < 1024; i++)
                {
                    if (_fdarray[i] == -1)
                        continue;
                    FD_SET(_fdarray[i], &rfds);
                    if (max < _fdarray[i])
                        max = _fdarray[i];
                }
                struct timeval timeout = {3, 0};
                int n = select(max + 1, &rfds, nullptr, nullptr, &timeout);
                // 后面的四个参数都是输入输出型号的 , 有很大的坑
                // 使用select的时候 就需要 一个辅助数组
                std::cout << "select retval: " << n << std::endl;
                switch (n)
                {
                case 0:
                    /* 超时 */
                    logcout(WARNING, "select tmieout!! ");
                    break;
                case -1:
                    /* 出错了 */
                    logcout(WARNING, "select error ");
                    break;
                default:
                    /* sock 事件就绪了 */
                    logcout(NORMAL, "有事件到来 !!");
                    handerEvent(rfds);
                    break;
                }
            }
        }

        void handerEvent(fd_set &rfds)
        {
            // handerEvent 其中有多个事件读取就绪
            for (int i = 0; i < 1024; i++)
            {
                // 过滤掉非法的文件描述符
                if (_fdarray[i] == -1)
                    continue;

                /*监听事件就绪 : 新链接到来*/
                if (FD_ISSET(_fdarray[i], &rfds) && _fdarray[i] == _sock)
                {
                    /*_sock读取事件就绪 : 新链接到来*/
                    struct sockaddr_in caddr;
                    socklen_t len = sizeof(caddr); // 这里是 值-结果参数
                    int newsock = accept(_sock, (struct sockaddr *)&caddr, &len);
                    // 返回一个新的文件描符号 用于通信
                    if (newsock < 0)
                    {
                        logcout(ERROR, "accept a new link error!!");
                    }
                    logcout(NORMAL, "accept a new link success!!");
                    std::cout << "accept sock : " << newsock << std::endl;
                    // 到这里 lisent 的事件处理完成
                    // 读取的链接之后的新的newsock 应该托管给select
                    //  一般而言 要用select, 程序员需要自己维护一个合法的fd数组

                    // 将新的newsock 添加到_fdarray数组中
                    int i = 0;
                    for (i = 0; i < 1024; i++)
                    {
                        if (_fdarray[i] != -1)
                            continue;
                        else
                            break;
                    }
                    if (i == 1024)
                    {
                        logcout(WARNING, "server if full please wait!!");
                        close(newsock);
                    }
                    else
                    {
                        _fdarray[i] = newsock;
                    }
                    logcout(NORMAL, "newsock add _fdarray!!");
                }
                else if (FD_ISSET(_fdarray[i], &rfds))
                {
                    // 正常的通信事件
                    Recver(_fdarray[i], i);
                }
                else continue;
            }
        }

        void Recver(int sock, int pos)
        {
            // 到这里的读取事件
            char buf[1024];                                  // 这里读取的不一定是 读取一个完整的协议 ,所以要打while循环,
            ssize_t n = recv(sock, buf, sizeof(buf) - 1, 0); // 这里不会被阻塞, 但是在 while循环中第二次读取的时候 不一定不会被阻塞
            if (n > 0)
            {
                buf[n] = 0;
                std::cout << "server 读取到一个消息: " << buf << std::endl;

                std::string str = buf;
                send(sock, str.c_str(), str.size(),0);
            }
            else if (n == 0)
            {
                close(sock);
                _fdarray[pos] = -1;
                logcout(NORMAL, "client quit!!");
            }
            else
            {
                close(sock);
                _fdarray[pos] = -1;
                logcout(ERROR, "recv error");
            }
        }

    private:
        uint16_t _port;
        std::string _ip;
        int _sock;
        int *_fdarray;
    };

    const uint16_t SelectServer::defport = 20000;
    // const int SelectServer::gbacklog = 20;
}


/*select 的 特点
    1.select 能同时等待的文件 fd 是由上限的, 除非修改内核 , 但是也是有上限的
    2.必须借助第三方的数组, 来维护合法的fd
    3.select 的大部分参数是输出型的 ,调用select之前,必须重新设定
    4.select 采用位图, 内核->用户, 用户->内核 来回的数据拷贝, 浪费时间
*/
